using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.OracleClient;
using System.Text.RegularExpressions;
using Oracle.DataAccess.Client;
using Microsoft.Practices.RepositoryFactory.SchemaDiscovery.ObjectModel.Base;

namespace Microsoft.Practices.RepositoryFactory.SchemaDiscovery.Translators.Oracle.Data
{
    /// <summary>
    /// This utility class is aimed at bypassing the System.Data.Common.DbType class
    /// because of its many shortcomings.  Instead, the enumerations System.Data.OracleClient.OracleType
    /// and OracleClient.Client.OracleDbType are mapped to an &quot;abstract&quot; database type in
    /// the form of <see cref="DatabaseType"/>.
    /// </summary>
    internal static class OracleDataTypeConverter
    {
        #region Public Implementation

        /// <summary>
        /// Converts from the DatabaseType enumeration to a System.Data.OracleClient.OracleType.
        /// </summary>
        /// <param name="dbType">A DatabaseType</param>
        /// <returns>An OracleType</returns>
        public static OracleType DatabaseType2OracleType(DatabaseType dbType)
        {
            switch (dbType)
            {
                case DatabaseType.BFile: return OracleType.BFile;
                case DatabaseType.BinaryDouble: return OracleType.Double;
                case DatabaseType.BinaryFloat: return OracleType.Float;
                case DatabaseType.Blob: return OracleType.Blob;
                case DatabaseType.Boolean: return OracleType.Char;
                case DatabaseType.Byte: return OracleType.Byte;
                case DatabaseType.Char: return OracleType.Char;
                case DatabaseType.Clob: return OracleType.Clob;
                case DatabaseType.Cursor: return OracleType.Cursor;
                case DatabaseType.Date: return OracleType.DateTime;
                case DatabaseType.Double: return OracleType.Double;
                case DatabaseType.Float: return OracleType.Float;
                case DatabaseType.Int16: return OracleType.Int16;
                case DatabaseType.Int32: return OracleType.Int32;
                case DatabaseType.Int64: return OracleType.Number;
                case DatabaseType.IntervalDayToSecond: return OracleType.IntervalDayToSecond;
                case DatabaseType.IntervalYearToMonth: return OracleType.IntervalYearToMonth;
                case DatabaseType.Long: return OracleType.LongVarChar;
                case DatabaseType.LongRaw: return OracleType.LongRaw;
                case DatabaseType.NChar: return OracleType.NChar;
                case DatabaseType.NClob: return OracleType.NClob;
                case DatabaseType.NText: return OracleType.LongVarChar;
                case DatabaseType.Number: return OracleType.Number;
                case DatabaseType.NVarChar: return OracleType.NVarChar;
                case DatabaseType.Raw: return OracleType.Raw;
                case DatabaseType.Real: return OracleType.Number;
                case DatabaseType.Single: return OracleType.Number;
                case DatabaseType.SmallDate: return OracleType.DateTime;
                case DatabaseType.SmallCurrency: return OracleType.Double;
                case DatabaseType.Text: return OracleType.LongVarChar;
                case DatabaseType.Timestamp: return OracleType.Timestamp;
                case DatabaseType.TimestampLocal: return OracleType.TimestampLocal;
                case DatabaseType.TimestampWithTz: return OracleType.TimestampWithTZ;
                case DatabaseType.VarChar: return OracleType.VarChar;
                case DatabaseType.Xml: return OracleType.VarChar;
                default: return OracleType.Raw;
            }
        }

        /// <summary>
        /// Converts from the DatabaseType enumeration to a Oracle.DataAccess.Client.OracleDbType.
        /// </summary>
        /// <param name="dbType">A DatabaseType</param>
        /// <returns>An ODP.NET OracleDbType</returns>
        public static OracleDbType DatabaseType2OracleDbType(DatabaseType dbType)
        {
            switch (dbType)
            {
                case DatabaseType.BFile: return OracleDbType.BFile;
                case DatabaseType.BinaryDouble: return OracleDbType.BinaryDouble;
                case DatabaseType.BinaryFloat: return OracleDbType.BinaryFloat;
                case DatabaseType.Blob: return OracleDbType.Blob;
                case DatabaseType.Boolean: return OracleDbType.Char; // No boolean in Oracle
                case DatabaseType.Byte: return OracleDbType.Byte;
                case DatabaseType.Char: return OracleDbType.Char;
                case DatabaseType.Clob: return OracleDbType.Clob;
                case DatabaseType.Cursor: return OracleDbType.RefCursor;
                case DatabaseType.Date: return OracleDbType.Date;
                case DatabaseType.Double: return OracleDbType.Double;
                case DatabaseType.Float: return OracleDbType.BinaryFloat;
                case DatabaseType.Int16: return OracleDbType.Int16;
                case DatabaseType.Int32: return OracleDbType.Int32;
                case DatabaseType.Int64: return OracleDbType.Int64;
                case DatabaseType.IntervalDayToSecond: return OracleDbType.IntervalDS;
                case DatabaseType.IntervalYearToMonth: return OracleDbType.IntervalYM;
                case DatabaseType.Long: return OracleDbType.Long;
                case DatabaseType.LongRaw: return OracleDbType.LongRaw;
                case DatabaseType.NChar: return OracleDbType.NChar;
                case DatabaseType.NClob: return OracleDbType.NClob;
                case DatabaseType.Number: return OracleDbType.Decimal;
                case DatabaseType.NVarChar: return OracleDbType.NVarchar2;
                case DatabaseType.Raw: return OracleDbType.Raw;
                case DatabaseType.Real: return OracleDbType.Decimal;
                case DatabaseType.Single: return OracleDbType.Single;
                case DatabaseType.Timestamp: return OracleDbType.TimeStamp;
                case DatabaseType.TimestampWithTz: return OracleDbType.TimeStampLTZ;
                case DatabaseType.TimestampLocal: return OracleDbType.TimeStampTZ;
                case DatabaseType.VarChar: return OracleDbType.Varchar2;
                case DatabaseType.Xml: return OracleDbType.XmlType;
                default: return OracleDbType.Raw;
            }

        }

        /// <summary>
        /// Converts a System.Data.OracleClient.OracleType to a DatabaseType.
        /// </summary>
        /// <param name="enumObj">An OracleType enumeration</param>
        /// <returns>A DatabaseType</returns>
        public static DatabaseType OracleType2DatabaseType(object enumObj)
        {
            switch (((OracleType)enumObj))
            {
                case OracleType.BFile: return DatabaseType.BFile;
                case OracleType.Blob: return DatabaseType.Blob;
                case OracleType.Byte: return DatabaseType.Byte;
                case OracleType.Char: return DatabaseType.Char;
                case OracleType.Clob: return DatabaseType.Blob;
                case OracleType.Cursor: return DatabaseType.Cursor;
                case OracleType.DateTime: return DatabaseType.Date;
                case OracleType.Double: return DatabaseType.Double;
                case OracleType.Float: return DatabaseType.Float;
                case OracleType.Int16: return DatabaseType.Int16;
                case OracleType.Int32: return DatabaseType.Int32;
                case OracleType.IntervalDayToSecond: return DatabaseType.IntervalDayToSecond;
                case OracleType.IntervalYearToMonth: return DatabaseType.IntervalYearToMonth;
                case OracleType.LongRaw: return DatabaseType.LongRaw;
                case OracleType.LongVarChar: return DatabaseType.VarChar;
                case OracleType.NChar: return DatabaseType.NChar;
                case OracleType.NClob: return DatabaseType.NClob;
                case OracleType.Number: return DatabaseType.Decimal;
                case OracleType.NVarChar: return DatabaseType.NVarChar;
                case OracleType.Raw: return DatabaseType.Raw;
                case OracleType.RowId: return DatabaseType.VarChar;
                case OracleType.SByte: return DatabaseType.Byte;
                case OracleType.Timestamp: return DatabaseType.Timestamp;
                case OracleType.TimestampLocal: return DatabaseType.TimestampLocal;
                case OracleType.TimestampWithTZ: return DatabaseType.TimestampWithTz;
                case OracleType.UInt16: return DatabaseType.Number;
                case OracleType.UInt32: return DatabaseType.Number;
                case OracleType.VarChar: return DatabaseType.VarChar;
                default: return DatabaseType.NotSupported;
            }
        }

        /// <summary>
        /// Converts an Oracle.DataAccess.Client.OracleDbType to a DatabaseType.
        /// </summary>
        /// <param name="enumObj">An OracleType enumeration</param>
        /// <returns>A DatabaseType</returns>
        public static DatabaseType OracleDbType2DatabaseType(object enumObj)
        {
            switch (((OracleDbType)enumObj))
            {
                case OracleDbType.BFile: return DatabaseType.BFile;
                case OracleDbType.BinaryDouble: return DatabaseType.BinaryDouble;
                case OracleDbType.BinaryFloat: return DatabaseType.BinaryFloat;
                case OracleDbType.Blob: return DatabaseType.Blob;
                case OracleDbType.Byte: return DatabaseType.Byte;
                case OracleDbType.Char: return DatabaseType.Char;
                case OracleDbType.Clob: return DatabaseType.Clob;
                case OracleDbType.Date: return DatabaseType.Date;
                case OracleDbType.Decimal: return DatabaseType.Decimal;
                case OracleDbType.Double: return DatabaseType.Double;
                case OracleDbType.Int16: return DatabaseType.Int16;
                case OracleDbType.Int32: return DatabaseType.Int32;
                case OracleDbType.Int64: return DatabaseType.Int64;
                case OracleDbType.IntervalDS: return DatabaseType.IntervalDayToSecond;
                case OracleDbType.IntervalYM: return DatabaseType.IntervalYearToMonth;
                case OracleDbType.Long: return DatabaseType.Long;
                case OracleDbType.LongRaw: return DatabaseType.LongRaw;
                case OracleDbType.NChar: return DatabaseType.NChar;
                case OracleDbType.NClob: return DatabaseType.NClob;
                case OracleDbType.NVarchar2: return DatabaseType.NVarChar;
                case OracleDbType.Raw: return DatabaseType.Raw;
                case OracleDbType.RefCursor: return DatabaseType.Cursor;
                case OracleDbType.Single: return DatabaseType.Single;
                case OracleDbType.TimeStamp: return DatabaseType.Timestamp;
                case OracleDbType.TimeStampLTZ: return DatabaseType.TimestampLocal;
                case OracleDbType.TimeStampTZ: return DatabaseType.TimestampWithTz;
                case OracleDbType.Varchar2: return DatabaseType.VarChar;
                case OracleDbType.XmlType: return DatabaseType.Xml;
                default: return DatabaseType.NotSupported;
            }
        }

        /// <summary>
        /// Converts a string to a DatabaseType.  Only native Oracle types are included.
        /// </summary>
        /// <param name="dataType">A string.</param>
        /// <returns>A DatabaseType</returns>
        public static DatabaseType String2DatabaseType(string dataType)
        {
            string dataTypeWithoutScale = RemoveScale(dataType);

            switch (dataTypeWithoutScale.ToLowerInvariant())
            {
                case "bfile": return DatabaseType.BFile;
                case "binary_double": return DatabaseType.BinaryDouble;
                case "binary_float": return DatabaseType.BinaryFloat;
                case "binary_integer": return DatabaseType.Int32;
                case "blob": return DatabaseType.Blob;
                case "boolean": return DatabaseType.Boolean;
                case "char": return DatabaseType.Char;
                case "clob": return DatabaseType.Clob;
                case "date": return DatabaseType.Date;
                case "float": return DatabaseType.Float;
                case "interval day to second": return DatabaseType.IntervalDayToSecond;
                case "interval year to month": return DatabaseType.IntervalYearToMonth;
                case "long": return DatabaseType.Long;
                case "long raw": return DatabaseType.LongRaw;
                case "nchar": return DatabaseType.NChar;
                case "nclob": return DatabaseType.NClob;
                case "number": return DatabaseType.Number;
                case "nvarchar2": return DatabaseType.NVarChar;
                case "pls_integer": return DatabaseType.Int32;
                case "raw": return DatabaseType.Raw;
                case "real": return DatabaseType.Real;
                case "ref cursor": return DatabaseType.Cursor;
                case "rowid": return DatabaseType.Char;
                case "timestamp": return DatabaseType.Timestamp;
                case "timestamp with local time zone": return DatabaseType.TimestampLocal;
                case "timestamp with time zone": return DatabaseType.TimestampWithTz;
                case "urowid": return DatabaseType.Char;
                case "varchar2": return DatabaseType.VarChar;
                case "xmltype": return DatabaseType.Xml;
                default: return DatabaseType.NotSupported;
            }
        }

        /// <summary>
        /// Converts a DatabaseType to a .NET type.
        /// </summary>
        /// <param name="dbType">A DatabaseType</param>
        /// <returns>A .NET type</returns>
        public static Type DatabaseType2NetType(DatabaseType dbType)
        {
            switch (dbType)
            {
                case DatabaseType.BFile: return typeof(byte[]);
                case DatabaseType.BinaryDouble: return typeof(decimal);
                case DatabaseType.BinaryFloat: return typeof(decimal);
                case DatabaseType.Blob: return typeof(byte[]);
                case DatabaseType.Boolean: return typeof(Boolean);
                case DatabaseType.Byte: return typeof(byte);
                case DatabaseType.Char: return typeof(string);
                case DatabaseType.Clob: return typeof(string);
                case DatabaseType.Cursor: return typeof(Object);
                case DatabaseType.Date: return typeof(DateTime);
                case DatabaseType.Double: return typeof(double);
                case DatabaseType.Float: return typeof(decimal);
                case DatabaseType.Int16: return typeof(Int16);
                case DatabaseType.Int32: return typeof(Int32);
                case DatabaseType.Int64: return typeof(Int64);
                case DatabaseType.IntervalDayToSecond: return typeof(TimeSpan);
                case DatabaseType.IntervalYearToMonth: return typeof(Int64);
                case DatabaseType.Long: return typeof(string);
                case DatabaseType.LongRaw: return typeof(byte[]);
                case DatabaseType.NChar: return typeof(string);
                case DatabaseType.NClob: return typeof(string);
                case DatabaseType.NText: return typeof(string);
                case DatabaseType.NVarChar: return typeof(string);
                case DatabaseType.Object: return typeof(object);
                case DatabaseType.Raw: return typeof(byte[]);
                case DatabaseType.Real: return typeof(decimal);
                case DatabaseType.Single: return typeof(Single);
                case DatabaseType.Timestamp: return typeof(DateTime);
                case DatabaseType.TimestampLocal: return typeof(DateTime);
                case DatabaseType.TimestampWithTz: return typeof(DateTime);
                case DatabaseType.VarChar: return typeof(string);
                case DatabaseType.Xml: return typeof(string);
                default: return typeof(object);
            }
        }

        /// <summary>
        /// Removes scale from type.  Assumes there could be multiple instances of scale within
        /// the input string.  Also assumes that the precision is in the form (
        /// <example>
        /// TIMESTAMP(6) get converted to TIMESTAMP.
        /// </example>
        /// </summary>
        /// <param name="dataType">A string representing a data type that may include a scale.</param>
        /// <returns>A string representing a data type without scale.</returns>
        private static string RemoveScale(string dataType)
        {
            string dataTypeWithoutScale = Regex.Replace(dataType, @"\s\(\d+\)", String.Empty);
            dataTypeWithoutScale.Trim();

            return dataTypeWithoutScale;
        }

        #endregion
    }
}
